#region AppServices License

// Product: 
// AppServices
// http://appservices.codeplex.com
//
// Author:
// Pawel Idzikowski
// http://www.polenter.com
// mailto:idzikowski@polenter.com?subject=AppServices-Question
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the author(s) be held liable for any damages arising from
// the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
//    1. The origin of this software must not be misrepresented; you must not
//      claim that you wrote the original software. If you use this software
//      in a product, an acknowledgment in the product documentation would be
//      appreciated but is not required.
//
//    2. Altered source versions must be plainly marked as such, and must not
//      be misrepresented as being the original software.
//
//    3. This notice may not be removed or altered from any source distribution.

#endregion

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;

namespace Polenter.Services.Internal
{
    /// <summary>
    /// Estimates all public and non public fields and properties which are marked with ServiceAttribute and should be injected
    /// </summary>
    internal class InjectedMemberProvider
    {
        /// <summary>
        /// These members are searched for the ServiceAttribute
        /// </summary>
        private const BindingFlags InstanceBindingFlags =
            BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;

#if !Smartphone
        [ThreadStatic] 
#endif
        private static InjectedMemberInfoCollection _cache;

        public InjectedMemberInfo GetInjectedMembers(Type type)
        {
            InjectedMemberInfoCollection cache = getCache();
            if (!cache.Contains(type))
            {
                InjectedMemberInfo info = createInjectedMemberInfo(type);
                cache.Add(info);
            }
            return cache[type];
        }

        private static InjectedMemberInfoCollection getCache()
        {
            if (_cache==null)
            {
                _cache=new InjectedMemberInfoCollection();
            }
            return _cache;
        }

        private static InjectedMemberInfo createInjectedMemberInfo(Type type)
        {
            PropertyInfo[] properties = getInjectedProperties(type);
            FieldInfo[] fields = getInjectedFields(type);
            var info = new InjectedMemberInfo(type, properties, fields);
            return info;
        }


        private static PropertyInfo[] getInjectedProperties(Type type)
        {
            PropertyInfo[] properties = type.GetProperties(InstanceBindingFlags);
            var result = new List<PropertyInfo>();
            foreach (PropertyInfo info in properties)
            {
                if (shouldBeInjected(info))
                {
                    result.Add(info);
                }
            }
            return result.ToArray();
        }

        private static FieldInfo[] getInjectedFields(Type type)
        {
            FieldInfo[] fields = type.GetFields(InstanceBindingFlags);
            var result = new List<FieldInfo>();
            foreach (FieldInfo info in fields)
            {
                if (shouldBeInjected(info))
                {
                    result.Add(info);
                }
            }
            return result.ToArray();
        }

        private static bool shouldBeInjected(MemberInfo info)
        {
            object[] attributes = info.GetCustomAttributes(typeof (ServiceAttribute), false);

            return attributes.Length > 0;
        }
    }

    internal class InjectedMemberInfo
    {
        public InjectedMemberInfo(Type type, PropertyInfo[] properties, FieldInfo[] fields)
        {
            Type = type;
            Properties = properties;
            Fields = fields;
        }

        public Type Type { get; set; }
        public PropertyInfo[] Properties { get; set; }
        public FieldInfo[] Fields { get; set; }
    }

    internal class InjectedMemberInfoCollection : KeyedCollection<Type, InjectedMemberInfo>
    {
        protected override Type GetKeyForItem(InjectedMemberInfo item)
        {
            return item.Type;
        }
    }
}